/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.hadoop.hbase;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder.ModifyableColumnFamilyDescriptor;
import org.apache.hadoop.hbase.client.CoprocessorDescriptor;
import org.apache.hadoop.hbase.client.CoprocessorDescriptorBuilder;
import org.apache.hadoop.hbase.client.Durability;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder.ModifyableTableDescriptor;
import org.apache.hadoop.hbase.exceptions.DeserializationException;
import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.yetus.audience.InterfaceAudience;

/**
 * HTableDescriptor contains the details about an HBase table  such as the descriptors of
 * all the column families, is the table a catalog table, <code> hbase:meta </code>,
 * if the table is read only, the maximum size of the memstore,
 * when the region split should occur, coprocessors associated with it etc...
 * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0.
 *             Use {@link TableDescriptorBuilder} to build {@link HTableDescriptor}.
 */
@Deprecated
@InterfaceAudience.Public
public class HTableDescriptor implements TableDescriptor, Comparable<HTableDescriptor> {
    public static final String SPLIT_POLICY = TableDescriptorBuilder.SPLIT_POLICY;
    public static final String MAX_FILESIZE = TableDescriptorBuilder.MAX_FILESIZE;
    public static final String OWNER = TableDescriptorBuilder.OWNER;
    public static final Bytes OWNER_KEY = TableDescriptorBuilder.OWNER_KEY;
    public static final String READONLY = TableDescriptorBuilder.READONLY;
    public static final String COMPACTION_ENABLED = TableDescriptorBuilder.COMPACTION_ENABLED;
    public static final String SPLIT_ENABLED = TableDescriptorBuilder.SPLIT_ENABLED;
    public static final String MERGE_ENABLED = TableDescriptorBuilder.MERGE_ENABLED;
    public static final String MEMSTORE_FLUSHSIZE = TableDescriptorBuilder.MEMSTORE_FLUSHSIZE;
    public static final String FLUSH_POLICY = TableDescriptorBuilder.FLUSH_POLICY;
    public static final String IS_ROOT = "IS_ROOT";
    public static final String IS_META = TableDescriptorBuilder.IS_META;
    public static final String DURABILITY = TableDescriptorBuilder.DURABILITY;
    public static final String REGION_REPLICATION = TableDescriptorBuilder.REGION_REPLICATION;
    public static final String REGION_MEMSTORE_REPLICATION = TableDescriptorBuilder.REGION_MEMSTORE_REPLICATION;
    public static final String NORMALIZATION_ENABLED = TableDescriptorBuilder.NORMALIZATION_ENABLED;
    public static final String NORMALIZER_TARGET_REGION_COUNT = TableDescriptorBuilder.NORMALIZER_TARGET_REGION_COUNT;
    public static final String NORMALIZER_TARGET_REGION_SIZE = TableDescriptorBuilder.NORMALIZER_TARGET_REGION_SIZE;
    public static final String PRIORITY = TableDescriptorBuilder.PRIORITY;
    public static final boolean DEFAULT_READONLY = TableDescriptorBuilder.DEFAULT_READONLY;
    public static final boolean DEFAULT_COMPACTION_ENABLED = TableDescriptorBuilder.DEFAULT_COMPACTION_ENABLED;
    public static final boolean DEFAULT_NORMALIZATION_ENABLED = TableDescriptorBuilder.DEFAULT_NORMALIZATION_ENABLED;
    public static final long DEFAULT_MEMSTORE_FLUSH_SIZE = TableDescriptorBuilder.DEFAULT_MEMSTORE_FLUSH_SIZE;
    public static final int DEFAULT_REGION_REPLICATION = TableDescriptorBuilder.DEFAULT_REGION_REPLICATION;
    public static final boolean DEFAULT_REGION_MEMSTORE_REPLICATION = TableDescriptorBuilder.DEFAULT_REGION_MEMSTORE_REPLICATION;
    protected final ModifyableTableDescriptor delegatee;

    /**
     * Construct a table descriptor specifying a TableName object
     * @param name Table name.
     * @see <a href="https://issues.apache.org/jira/browse/HBASE-174">HADOOP-1581 HBASE: (HBASE-174) Un-openable tablename bug</a>
     */
    public HTableDescriptor(final TableName name) {
        this(new ModifyableTableDescriptor(name));
    }

    /**
     * Construct a table descriptor by cloning the descriptor passed as a parameter.
     * <p>
     * Makes a deep copy of the supplied descriptor.
     * Can make a modifiable descriptor from an ImmutableHTableDescriptor.
     * @param desc The descriptor.
     */
    public HTableDescriptor(final HTableDescriptor desc) {
        this(desc, true);
    }

    protected HTableDescriptor(final HTableDescriptor desc, boolean deepClone) {
        this(deepClone ? new ModifyableTableDescriptor(desc.getTableName(), desc) : desc.delegatee);
    }

    public HTableDescriptor(final TableDescriptor desc) {
        this(new ModifyableTableDescriptor(desc.getTableName(), desc));
    }

    /**
     * Construct a table descriptor by cloning the descriptor passed as a parameter
     * but using a different table name.
     * <p>
     * Makes a deep copy of the supplied descriptor.
     * Can make a modifiable descriptor from an ImmutableHTableDescriptor.
     * @param name Table name.
     * @param desc The descriptor.
     */
    public HTableDescriptor(final TableName name, final HTableDescriptor desc) {
        this(new ModifyableTableDescriptor(name, desc));
    }

    protected HTableDescriptor(ModifyableTableDescriptor delegatee) {
        this.delegatee = delegatee;
    }

    /**
     * This is vestigial API. It will be removed in 3.0.
     *
     * @return always return the false
     */
    public boolean isRootRegion() {
        return false;
    }

    /**
     * Checks if this table is <code> hbase:meta </code>
     * region.
     *
     * @return true if this table is <code> hbase:meta </code>
     * region
     */
    @Override
    public boolean isMetaRegion() {
        return delegatee.isMetaRegion();
    }

    /**
     * Checks if the table is a <code>hbase:meta</code> table
     *
     * @return true if table is <code> hbase:meta </code> region.
     */
    @Override
    public boolean isMetaTable() {
        return delegatee.isMetaTable();
    }

    /**
     * @return Getter for fetching an unmodifiable map.
     */
    @Override
    public Map<Bytes, Bytes> getValues() {
        return delegatee.getValues();
    }

    /**
     * Setter for storing metadata as a (key, value) pair in map
     *
     * @param key The key.
     * @param value The value. If null, removes the setting.
     */
    public HTableDescriptor setValue(byte[] key, byte[] value) {
        getDelegateeForModification().setValue(key, value);
        return this;
    }

    /*
     * Setter for storing metadata as a (key, value) pair in map
     *
     * @param key The key.
     * @param value The value. If null, removes the setting.
     */
    public HTableDescriptor setValue(final Bytes key, final Bytes value) {
        getDelegateeForModification().setValue(key, value);
        return this;
    }

    /**
     * Setter for storing metadata as a (key, value) pair in map
     *
     * @param key The key.
     * @param value The value. If null, removes the setting.
     */
    public HTableDescriptor setValue(String key, String value) {
        getDelegateeForModification().setValue(key, value);
        return this;
    }

    /**
     * Remove metadata represented by the key from the map
     *
     * @param key Key whose key and value we're to remove from HTableDescriptor
     * parameters.
     */
    public void remove(final String key) {
        getDelegateeForModification().removeValue(Bytes.toBytes(key));
    }

    /**
     * Remove metadata represented by the key from the map
     *
     * @param key Key whose key and value we're to remove from HTableDescriptor
     * parameters.
     */
    public void remove(Bytes key) {
        getDelegateeForModification().removeValue(key);
    }

    /**
     * Remove metadata represented by the key from the map
     *
     * @param key Key whose key and value we're to remove from HTableDescriptor
     * parameters.
     */
    public void remove(final byte[] key) {
        getDelegateeForModification().removeValue(key);
    }

    /**
     * Check if the readOnly flag of the table is set. If the readOnly flag is
     * set then the contents of the table can only be read from but not modified.
     *
     * @return true if all columns in the table should be read only
     */
    @Override
    public boolean isReadOnly() {
        return delegatee.isReadOnly();
    }

    /**
     * Setting the table as read only sets all the columns in the table as read
     * only. By default all tables are modifiable, but if the readOnly flag is
     * set to true then the contents of the table can only be read but not modified.
     *
     * @param readOnly True if all of the columns in the table should be read
     * only.
     */
    public HTableDescriptor setReadOnly(final boolean readOnly) {
        getDelegateeForModification().setReadOnly(readOnly);
        return this;
    }

    /**
     * Check if the compaction enable flag of the table is true. If flag is
     * false then no minor/major compactions will be done in real.
     *
     * @return true if table compaction enabled
     */
    @Override
    public boolean isCompactionEnabled() {
        return delegatee.isCompactionEnabled();
    }

    /**
     * Setting the table compaction enable flag.
     *
     * @param isEnable True if enable compaction.
     */
    public HTableDescriptor setCompactionEnabled(final boolean isEnable) {
        getDelegateeForModification().setCompactionEnabled(isEnable);
        return this;
    }

    /**
     * Check if the region split enable flag of the table is true. If flag is
     * false then no split will be done.
     *
     * @return true if table region split enabled
     */
    @Override
    public boolean isSplitEnabled() {
        return delegatee.isSplitEnabled();
    }

    /**
     * Setting the table region split enable flag.
     *
     * @param isEnable True if enable split.
     */
    public HTableDescriptor setSplitEnabled(final boolean isEnable) {
        getDelegateeForModification().setSplitEnabled(isEnable);
        return this;
    }


    /**
     * Check if the region merge enable flag of the table is true. If flag is
     * false then no merge will be done.
     *
     * @return true if table region merge enabled
     */
    @Override
    public boolean isMergeEnabled() {
        return delegatee.isMergeEnabled();
    }

    /**
     * Setting the table region merge enable flag.
     *
     * @param isEnable True if enable merge.
     */
    public HTableDescriptor setMergeEnabled(final boolean isEnable) {
        getDelegateeForModification().setMergeEnabled(isEnable);
        return this;
    }

    /**
     * Check if normalization enable flag of the table is true. If flag is
     * false then no region normalizer won't attempt to normalize this table.
     *
     * @return true if region normalization is enabled for this table
     */
    @Override
    public boolean isNormalizationEnabled() {
        return delegatee.isNormalizationEnabled();
    }

    /**
     * Setting the table normalization enable flag.
     *
     * @param isEnable True if enable normalization.
     */
    public HTableDescriptor setNormalizationEnabled(final boolean isEnable) {
        getDelegateeForModification().setNormalizationEnabled(isEnable);
        return this;
    }

    @Override
    public int getNormalizerTargetRegionCount() {
        return getDelegateeForModification().getNormalizerTargetRegionCount();
    }

    public HTableDescriptor setNormalizerTargetRegionCount(final int regionCount) {
        getDelegateeForModification().setNormalizerTargetRegionCount(regionCount);
        return this;
    }

    @Override
    public long getNormalizerTargetRegionSize() {
        return getDelegateeForModification().getNormalizerTargetRegionSize();
    }

    public HTableDescriptor setNormalizerTargetRegionSize(final long regionSize) {
        getDelegateeForModification().setNormalizerTargetRegionSize(regionSize);
        return this;
    }

    /**
     * Sets the {@link Durability} setting for the table. This defaults to Durability.USE_DEFAULT.
     * @param durability enum value
     */
    public HTableDescriptor setDurability(Durability durability) {
        getDelegateeForModification().setDurability(durability);
        return this;
    }

    /**
     * Returns the durability setting for the table.
     * @return durability setting for the table.
     */
    @Override
    public Durability getDurability() {
        return delegatee.getDurability();
    }

    /**
     * Get the name of the table
     *
     * @return TableName
     */
    @Override
    public TableName getTableName() {
        return delegatee.getTableName();
    }

    /**
     * Get the name of the table as a String
     *
     * @return name of table as a String
     */
    public String getNameAsString() {
        return delegatee.getTableName().getNameAsString();
    }

    /**
     * This sets the class associated with the region split policy which
     * determines when a region split should occur.  The class used by
     * default is defined in org.apache.hadoop.hbase.regionserver.RegionSplitPolicy
     * @param clazz the class name
     */
    public HTableDescriptor setRegionSplitPolicyClassName(String clazz) {
        getDelegateeForModification().setRegionSplitPolicyClassName(clazz);
        return this;
    }

    /**
     * This gets the class associated with the region split policy which
     * determines when a region split should occur.  The class used by
     * default is defined in org.apache.hadoop.hbase.regionserver.RegionSplitPolicy
     *
     * @return the class name of the region split policy for this table.
     * If this returns null, the default split policy is used.
     */
    @Override
    public String getRegionSplitPolicyClassName() {
        return delegatee.getRegionSplitPolicyClassName();
    }

    /**
     * Returns the maximum size upto which a region can grow to after which a region
     * split is triggered. The region size is represented by the size of the biggest
     * store file in that region.
     *
     * @return max hregion size for table, -1 if not set.
     *
     * @see #setMaxFileSize(long)
     */
    @Override
    public long getMaxFileSize() {
        return delegatee.getMaxFileSize();
    }

    /**
     * Sets the maximum size upto which a region can grow to after which a region
     * split is triggered. The region size is represented by the size of the biggest
     * store file in that region, i.e. If the biggest store file grows beyond the
     * maxFileSize, then the region split is triggered. This defaults to a value of
     * 256 MB.
     * <p>
     * This is not an absolute value and might vary. Assume that a single row exceeds
     * the maxFileSize then the storeFileSize will be greater than maxFileSize since
     * a single row cannot be split across multiple regions
     * </p>
     *
     * @param maxFileSize The maximum file size that a store file can grow to
     * before a split is triggered.
     */
    public HTableDescriptor setMaxFileSize(long maxFileSize) {
        getDelegateeForModification().setMaxFileSize(maxFileSize);
        return this;
    }

    /**
     * Returns the size of the memstore after which a flush to filesystem is triggered.
     *
     * @return memory cache flush size for each hregion, -1 if not set.
     *
     * @see #setMemStoreFlushSize(long)
     */
    @Override
    public long getMemStoreFlushSize() {
        return delegatee.getMemStoreFlushSize();
    }

    /**
     * Represents the maximum size of the memstore after which the contents of the
     * memstore are flushed to the filesystem. This defaults to a size of 64 MB.
     *
     * @param memstoreFlushSize memory cache flush size for each hregion
     */
    public HTableDescriptor setMemStoreFlushSize(long memstoreFlushSize) {
        getDelegateeForModification().setMemStoreFlushSize(memstoreFlushSize);
        return this;
    }

    /**
     * This sets the class associated with the flush policy which determines determines the stores
     * need to be flushed when flushing a region. The class used by default is defined in
     * org.apache.hadoop.hbase.regionserver.FlushPolicy.
     * @param clazz the class name
     */
    public HTableDescriptor setFlushPolicyClassName(String clazz) {
        getDelegateeForModification().setFlushPolicyClassName(clazz);
        return this;
    }

    /**
     * This gets the class associated with the flush policy which determines the stores need to be
     * flushed when flushing a region. The class used by default is defined in
     * org.apache.hadoop.hbase.regionserver.FlushPolicy.
     * @return the class name of the flush policy for this table. If this returns null, the default
     *         flush policy is used.
     */
    @Override
    public String getFlushPolicyClassName() {
        return delegatee.getFlushPolicyClassName();
    }

    /**
     * Adds a column family.
     * For the updating purpose please use {@link #modifyFamily(HColumnDescriptor)} instead.
     * @param family HColumnDescriptor of family to add.
     */
    public HTableDescriptor addFamily(final HColumnDescriptor family) {
        getDelegateeForModification().setColumnFamily(family);
        return this;
    }

    /**
     * Modifies the existing column family.
     * @param family HColumnDescriptor of family to update
     * @return this (for chained invocation)
     */
    public HTableDescriptor modifyFamily(final HColumnDescriptor family) {
        getDelegateeForModification().modifyColumnFamily(family);
        return this;
    }

    /**
     * Checks to see if this table contains the given column family
     * @param familyName Family name or column name.
     * @return true if the table contains the specified family name
     */
    public boolean hasFamily(final byte[] familyName) {
        return delegatee.hasColumnFamily(familyName);
    }

    /**
     * @return Name of this table and then a map of all of the column family
     * descriptors.
     * @see #getNameAsString()
     */
    @Override
    public String toString() {
        return delegatee.toString();
    }

    /**
     * @return Name of this table and then a map of all of the column family
     * descriptors (with only the non-default column family attributes)
     */
    @Override
    public String toStringCustomizedValues() {
        return delegatee.toStringCustomizedValues();
    }

    /**
     * @return map of all table attributes formatted into string.
     */
    public String toStringTableAttributes() {
        return delegatee.toStringTableAttributes();
    }

    /**
     * Compare the contents of the descriptor with another one passed as a parameter.
     * Checks if the obj passed is an instance of HTableDescriptor, if yes then the
     * contents of the descriptors are compared.
     *
     * @return true if the contents of the the two descriptors exactly match
     *
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if(this == obj) {
            return true;
        }
        if(obj instanceof HTableDescriptor) {
            return delegatee.equals(((HTableDescriptor) obj).delegatee);
        }
        return false;
    }

    /**
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        return delegatee.hashCode();
    }

    // Comparable

    /**
     * Compares the descriptor with another descriptor which is passed as a parameter.
     * This compares the content of the two descriptors and not the reference.
     *
     * @return 0 if the contents of the descriptors are exactly matching,
     *         1 if there is a mismatch in the contents
     */
    @Override
    public int compareTo(final HTableDescriptor other) {
        return TableDescriptor.COMPARATOR.compare(this, other);
    }

    /**
     * Returns an unmodifiable collection of all the {@link HColumnDescriptor}
     * of all the column families of the table.
     * @deprecated since 2.0.0 and will be removed in 3.0.0. Use {@link #getColumnFamilies()} instead.
     * @return Immutable collection of {@link HColumnDescriptor} of all the
     * column families.
     * @see #getColumnFamilies()
     * @see <a href="https://issues.apache.org/jira/browse/HBASE-18008">HBASE-18008</a>
     */
    @Deprecated
    public Collection<HColumnDescriptor> getFamilies() {
        return Stream.of(delegatee.getColumnFamilies()).map(this::toHColumnDescriptor).collect(Collectors.toList());
    }

    /**
     * Returns the configured replicas per region
     */
    @Override
    public int getRegionReplication() {
        return delegatee.getRegionReplication();
    }

    /**
     * Sets the number of replicas per region.
     * @param regionReplication the replication factor per region
     */
    public HTableDescriptor setRegionReplication(int regionReplication) {
        getDelegateeForModification().setRegionReplication(regionReplication);
        return this;
    }

    /**
     * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0.
     *             Use {@link #hasRegionMemStoreReplication()} instead
     */
    @Deprecated
    public boolean hasRegionMemstoreReplication() {
        return hasRegionMemStoreReplication();
    }

    /**
     * @return true if the read-replicas memstore replication is enabled.
     */
    @Override
    public boolean hasRegionMemStoreReplication() {
        return delegatee.hasRegionMemStoreReplication();
    }

    /**
     * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0.
     *             Use {@link #setRegionMemStoreReplication(boolean)} instead
     */
    @Deprecated
    public HTableDescriptor setRegionMemstoreReplication(boolean memstoreReplication) {
        return setRegionMemStoreReplication(memstoreReplication);
    }

    /**
     * Enable or Disable the memstore replication from the primary region to the replicas.
     * The replication will be used only for meta operations (e.g. flush, compaction, ...)
     *
     * @param memstoreReplication true if the new data written to the primary region
     *                                 should be replicated.
     *                            false if the secondaries can tollerate to have new
     *                                  data only when the primary flushes the memstore.
     */
    public HTableDescriptor setRegionMemStoreReplication(boolean memstoreReplication) {
        getDelegateeForModification().setRegionMemStoreReplication(memstoreReplication);
        return this;
    }

    public HTableDescriptor setPriority(int priority) {
        getDelegateeForModification().setPriority(priority);
        return this;
    }

    @Override
    public int getPriority() {
        return delegatee.getPriority();
    }

    /**
     * Returns all the column family names of the current table. The map of
     * HTableDescriptor contains mapping of family name to HColumnDescriptors.
     * This returns all the keys of the family map which represents the column
     * family names of the table.
     *
     * @return Immutable sorted set of the keys of the families.
     * @deprecated As of release 2.0.0, this will be removed in HBase 3.0.0
     *             (<a href="https://issues.apache.org/jira/browse/HBASE-18008">HBASE-18008</a>).
     *             Use {@link #getColumnFamilyNames()}.
     */
    @Deprecated
    public Set<byte[]> getFamiliesKeys() {
        return delegatee.getColumnFamilyNames();
    }

    /**
     * Returns the count of the column families of the table.
     *
     * @return Count of column families of the table
     */
    @Override
    public int getColumnFamilyCount() {
        return delegatee.getColumnFamilyCount();
    }

    /**
     * Returns an array all the {@link HColumnDescriptor} of the column families
     * of the table.
     *
     * @return Array of all the HColumnDescriptors of the current table
     * @deprecated since 2.0.0 and will be removed in 3.0.0.
     * @see #getFamilies()
     * @see <a href="https://issues.apache.org/jira/browse/HBASE-18008">HBASE-18008</a>
     */
    @Deprecated
    @Override
    public HColumnDescriptor[] getColumnFamilies() {
        return Stream.of(delegatee.getColumnFamilies()).map(this::toHColumnDescriptor).toArray(size -> new HColumnDescriptor[size]);
    }

    /**
     * Returns the HColumnDescriptor for a specific column family with name as
     * specified by the parameter column.
     * @param column Column family name
     * @return Column descriptor for the passed family name or the family on
     * passed in column.
     * @deprecated since 2.0.0 and will be removed in 3.0.0. Use {@link #getColumnFamily(byte[])}
     *   instead.
     * @see #getColumnFamily(byte[])
     * @see <a href="https://issues.apache.org/jira/browse/HBASE-18008">HBASE-18008</a>
     */
    @Deprecated
    public HColumnDescriptor getFamily(final byte[] column) {
        return toHColumnDescriptor(delegatee.getColumnFamily(column));
    }


    /**
     * Removes the HColumnDescriptor with name specified by the parameter column
     * from the table descriptor
     *
     * @param column Name of the column family to be removed.
     * @return Column descriptor for the passed family name or the family on
     * passed in column.
     */
    public HColumnDescriptor removeFamily(final byte[] column) {
        return toHColumnDescriptor(getDelegateeForModification().removeColumnFamily(column));
    }

    /**
     * Return a HColumnDescriptor for user to keep the compatibility as much as possible.
     * @param desc read-only ColumnFamilyDescriptor
     * @return The older implementation of ColumnFamilyDescriptor
     */
    protected HColumnDescriptor toHColumnDescriptor(ColumnFamilyDescriptor desc) {
        if(desc == null) {
            return null;
        } else if(desc instanceof ModifyableColumnFamilyDescriptor) {
            return new HColumnDescriptor((ModifyableColumnFamilyDescriptor) desc);
        } else if(desc instanceof HColumnDescriptor) {
            return (HColumnDescriptor) desc;
        } else {
            return new HColumnDescriptor(new ModifyableColumnFamilyDescriptor(desc));
        }
    }

    /**
     * Add a table coprocessor to this table. The coprocessor
     * type must be org.apache.hadoop.hbase.coprocessor.RegionCoprocessor.
     * It won't check if the class can be loaded or not.
     * Whether a coprocessor is loadable or not will be determined when
     * a region is opened.
     * @param className Full class name.
     * @throws IOException
     */
    public HTableDescriptor addCoprocessor(String className) throws IOException {
        getDelegateeForModification().setCoprocessor(className);
        return this;
    }

    /**
     * Add a table coprocessor to this table. The coprocessor
     * type must be org.apache.hadoop.hbase.coprocessor.RegionCoprocessor.
     * It won't check if the class can be loaded or not.
     * Whether a coprocessor is loadable or not will be determined when
     * a region is opened.
     * @param jarFilePath Path of the jar file. If it's null, the class will be
     * loaded from default classloader.
     * @param className Full class name.
     * @param priority Priority
     * @param kvs Arbitrary key-value parameter pairs passed into the coprocessor.
     * @throws IOException
     */
    public HTableDescriptor addCoprocessor(String className, Path jarFilePath, int priority, final Map<String, String> kvs) throws IOException {
        getDelegateeForModification().setCoprocessor(
                CoprocessorDescriptorBuilder.newBuilder(className).setJarPath(jarFilePath == null ? null : jarFilePath.toString())
                        .setPriority(priority).setProperties(kvs == null ? Collections.emptyMap() : kvs).build());
        return this;
    }

    /**
     * Add a table coprocessor to this table. The coprocessor
     * type must be org.apache.hadoop.hbase.coprocessor.RegionCoprocessor.
     * It won't check if the class can be loaded or not.
     * Whether a coprocessor is loadable or not will be determined when
     * a region is opened.
     * @param specStr The Coprocessor specification all in in one String formatted so matches
     * {@link HConstants#CP_HTD_ATTR_VALUE_PATTERN}
     * @throws IOException
     */
    public HTableDescriptor addCoprocessorWithSpec(final String specStr) throws IOException {
        getDelegateeForModification().setCoprocessorWithSpec(specStr);
        return this;
    }

    /**
     * Check if the table has an attached co-processor represented by the name className
     *
     * @param classNameToMatch - Class name of the co-processor
     * @return true of the table has a co-processor className
     */
    @Override
    public boolean hasCoprocessor(String classNameToMatch) {
        return delegatee.hasCoprocessor(classNameToMatch);
    }

    @Override
    public Collection<CoprocessorDescriptor> getCoprocessorDescriptors() {
        return delegatee.getCoprocessorDescriptors();
    }

    /**
     * Remove a coprocessor from those set on the table
     * @param className Class name of the co-processor
     */
    public void removeCoprocessor(String className) {
        getDelegateeForModification().removeCoprocessor(className);
    }

    public final static String NAMESPACE_FAMILY_INFO = TableDescriptorBuilder.NAMESPACE_FAMILY_INFO;
    public final static byte[] NAMESPACE_FAMILY_INFO_BYTES = TableDescriptorBuilder.NAMESPACE_FAMILY_INFO_BYTES;
    public final static byte[] NAMESPACE_COL_DESC_BYTES = TableDescriptorBuilder.NAMESPACE_COL_DESC_BYTES;

    /** Table descriptor for namespace table */
    public static final HTableDescriptor NAMESPACE_TABLEDESC = new HTableDescriptor(TableDescriptorBuilder.NAMESPACE_TABLEDESC);

    /**
     * @deprecated since 0.94.1
     * @see <a href="https://issues.apache.org/jira/browse/HBASE-6188">HBASE-6188</a>
     */
    @Deprecated
    public HTableDescriptor setOwner(User owner) {
        getDelegateeForModification().setOwner(owner);
        return this;
    }

    /**
     * @deprecated since 0.94.1
     * @see <a href="https://issues.apache.org/jira/browse/HBASE-6188">HBASE-6188</a>
     */
    // used by admin.rb:alter(table_name,*args) to update owner.
    @Deprecated
    public HTableDescriptor setOwnerString(String ownerString) {
        getDelegateeForModification().setOwnerString(ownerString);
        return this;
    }

    /**
     * @deprecated since 0.94.1
     * @see <a href="https://issues.apache.org/jira/browse/HBASE-6188">HBASE-6188</a>
     */
    @Override
    @Deprecated
    public String getOwnerString() {
        return delegatee.getOwnerString();
    }

    /**
     * @return This instance serialized with pb with pb magic prefix
     * @see #parseFrom(byte[])
     */
    public byte[] toByteArray() {
        return TableDescriptorBuilder.toByteArray(delegatee);
    }

    /**
     * @param bytes A pb serialized {@link HTableDescriptor} instance with pb magic prefix
     * @return An instance of {@link HTableDescriptor} made from <code>bytes</code>
     * @throws DeserializationException
     * @throws IOException
     * @see #toByteArray()
     */
    public static HTableDescriptor parseFrom(final byte[] bytes) throws DeserializationException, IOException {
        TableDescriptor desc = TableDescriptorBuilder.parseFrom(bytes);
        if(desc instanceof ModifyableTableDescriptor) {
            return new HTableDescriptor((ModifyableTableDescriptor) desc);
        } else {
            return new HTableDescriptor(desc);
        }
    }

    /**
     * Getter for accessing the configuration value by key
     */
    public String getConfigurationValue(String key) {
        return delegatee.getValue(key);
    }

    /**
     * Getter for fetching an unmodifiable map.
     */
    public Map<String, String> getConfiguration() {
        return delegatee.getValues().entrySet().stream().collect(Collectors
                .toMap(e -> Bytes.toString(e.getKey().get(), e.getKey().getOffset(), e.getKey().getLength()),
                        e -> Bytes.toString(e.getValue().get(), e.getValue().getOffset(), e.getValue().getLength())));
    }

    /**
     * Setter for storing a configuration setting in map.
     * @param key Config key. Same as XML config key e.g. hbase.something.or.other.
     * @param value String value. If null, removes the setting.
     */
    public HTableDescriptor setConfiguration(String key, String value) {
        getDelegateeForModification().setValue(key, value);
        return this;
    }

    /**
     * Remove a config setting represented by the key from the map
     */
    public void removeConfiguration(final String key) {
        getDelegateeForModification().removeValue(Bytes.toBytes(key));
    }

    @Override
    public Bytes getValue(Bytes key) {
        return delegatee.getValue(key);
    }

    @Override
    public String getValue(String key) {
        return delegatee.getValue(key);
    }

    @Override
    public byte[] getValue(byte[] key) {
        return delegatee.getValue(key);
    }

    @Override
    public Set<byte[]> getColumnFamilyNames() {
        return delegatee.getColumnFamilyNames();
    }

    @Override
    public boolean hasColumnFamily(byte[] name) {
        return delegatee.hasColumnFamily(name);
    }

    @Override
    public ColumnFamilyDescriptor getColumnFamily(byte[] name) {
        return delegatee.getColumnFamily(name);
    }

    protected ModifyableTableDescriptor getDelegateeForModification() {
        return delegatee;
    }
}
